﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using Topshelf;

namespace nwrole.Module.lib
{
    public class ModuleStarter
    {
        private IModule _module;
 
        public ModuleStarter(IModule module, string[] args, int superModulePort = 5000)
        {
            _module = module;

            string fullPath = Assembly.GetCallingAssembly().Location;

            string moduleName = Path.GetFileName(Path.GetDirectoryName(fullPath));
            string name = Path.GetFileNameWithoutExtension(fullPath);

            string serviceName = moduleName + "_" + name;

            if (args.Contains("setup", StringComparer.InvariantCultureIgnoreCase))
            {
                var app = new ModuleApp(_module, fullPath, serviceName, superModulePort);
                app.Setup();
                return;
            }

            int exitCode = (int)HostFactory.Run(host =>
            {
                //host.AddCommandLineDefinition("setup", f => { fooBar = f; });
                //host.ApplyCommandLine();

                host.Service<ModuleApp>(service =>
                {
                    service.ConstructUsing(() => new ModuleApp(_module, fullPath, serviceName, superModulePort));
                    service.WhenStarted(a => a.Start());
                    service.WhenStopped(a => a.Stop());
                });

                host.SetDescription("nwrole module");
                host.SetDisplayName(serviceName);
                host.SetServiceName(serviceName);
                host.StartManually();
                host.RunAsLocalSystem();
            });
        }

    }

    class ModuleApp
    {
        private IModule _module;

        private IModuleDebug _moduleDebug;

        private ModuleSettings _settings;

        private Thread _thread;

        private int _superModulePort;
        private int _startDelay = 0;
        private string _moduleName;
        
        public ModuleApp(IModule module, string fullPath, string serviceName, int superModulePort)
        {
            _module = module;
            _superModulePort = superModulePort;

            _moduleDebug = new ModuleDebug(superModulePort, serviceName);

            _moduleName = Path.GetDirectoryName(fullPath);

            string logFolder = Path.Combine(_moduleName, "log");
            if(!Directory.Exists(logFolder))
                Directory.CreateDirectory(logFolder);

            string settingsFile = Path.Combine(_moduleName, "ModuleSettings.xml");

            _settings = new ModuleSettings(settingsFile);
        }

        public void Setup()
        {
            //_module.Setup(_moduleDebug, _settings);
            LoadConfig(); // Fill properties with default values
            SaveConfig();
            _settings.Save();
        }

        /*public void Start()
        {
            try
            {
                Console.WriteLine("ModuleApp Starting");

                _module.Start(_moduleDebug, _settings);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                Console.ReadKey();
                throw;
            }
        }*/

        public void Start()
        {
            _thread = new Thread(ThreadStart);
            _thread.Start();
        }

        private void ThreadStart()
        {
            Trace.TraceInformation("Module starting: " + _moduleName);

            LoadConfig();

            bool runAgain = true;

            while (runAgain)
            {
                runAgain = false;
                System.Threading.Thread.Sleep(_startDelay);
                try
                {
                    Trace.TraceInformation("Module running: " + _moduleName);
                    //PluginState = PluginState.Running;
                    _module.Start(_moduleDebug);

                    Trace.TraceInformation("Module Stopped (done): " + _moduleName);
                    //PluginState = PluginState.Stopped;
                }
                catch (Exception e)
                {
                    //PluginState = PluginState.Faulted;
                    Trace.TraceError("Module faulted, Unhandled exception: " + e);

                    if (bool.Parse(_settings.GetSetting("RestartWhenFaulted", false.ToString())))
                    {
                        runAgain = true;
                        //pluginManager.SendFaultEmail(PluginData.DisplayName + " has faulted!", "The plugin " + PluginData.DisplayName + " has faulted and will shortly be restarted. Reason for fault: Unhandled plugin exception: " + e);
                    }
                    else
                    {
                        //pluginManager.SendFaultEmail(PluginData.DisplayName + " has faulted!", "The plugin " + PluginData.DisplayName + " has faulted and is no longer running. Unhandled plugin exception: " + e);
                    }
                }
            }

        }

        public void Stop()
        {
            if (_thread != null && _thread.IsAlive)
            {
                Trace.TraceInformation("Module stopping: " + _moduleName);

                _module.Stop();
                _thread.Join(TimeSpan.FromSeconds(10));
                _thread = null;

                Trace.TraceInformation("Module stopped: " + _moduleName);
            }

            SaveConfig();
            _settings.Save();
        }

        private void LoadConfig()
        {
            foreach (PropertyInfo propertyInfo in _module.GetType().GetProperties())
            {
                foreach (Attribute attr in propertyInfo.GetCustomAttributes(true))
                {
                    ModuleConfigAttribute configAttr = attr as ModuleConfigAttribute;
                    if (configAttr != null)
                    {
                        string settingName = configAttr.Name ?? propertyInfo.Name;
                        string defaultValue = configAttr.DefaultValue != null ? configAttr.DefaultValue.ToString() : "";

                        string value = _settings.GetSetting(settingName, defaultValue);
                        if (!string.IsNullOrEmpty(value))
                            propertyInfo.SetValue(_module, Convert.ChangeType(value, propertyInfo.PropertyType), null);
                    }
                }
            }
        }

        private void SaveConfig()
        {
            foreach (PropertyInfo propertyInfo in _module.GetType().GetProperties())
            {
                foreach (Attribute attr in propertyInfo.GetCustomAttributes(true))
                {
                    ModuleConfigAttribute configAttr = attr as ModuleConfigAttribute;
                    if (configAttr != null)
                    {
                        string settingName = configAttr.Name ?? propertyInfo.Name;
                        object propValue = propertyInfo.GetValue(_module, null);
                        string value = propValue != null ? propValue.ToString() : "";

                        _settings.PutSetting(settingName, value);
                    }
                }
            }
        }
    }
}
